package com.xiyuan.smartutils;

import com.xiyuan.smartutils.function.SFunc2;
import com.xiyuan.smartutils.function.SFunc3;
import com.xiyuan.smartutils.function.SFunction;
import com.xiyuan.smartutils.lambda.Lambda;

import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * the description for class LambdaUtil
 *
 * @author xiyuan-lgz 2023-09-19 @version v1.0.0 新建与整理
 */
@SuppressWarnings("unchecked")
public final class LambdaUtils {
    
    
    private LambdaUtils() {}
    
    private static final Map<Object, Object> MAP = new ConcurrentHashMap<>();
    
    public static <P, R> SerializedLambda getSerializedLambda(SFunction<P, R> function) {
        return function.getSerializedLambda();
    }
    
    public static <P, P1, R> SerializedLambda getSerializedLambda(SFunc2<P, P1, R> function) {
        return function.getSerializedLambda();
    }
    
    public static <E, P> Class<E> getReturnType(SFunction<P, ?> function) {
        return function.getReturnType();
    }
    
    public static <E, P, P1> Class<E> getReturnType(SFunc2<P, P1, ?> function) {
        return function.getReturnType();
    }
    
    public static <P> Class<?>[] getParameterTypes(SFunction<P, ?> function) throws Exception {
        return function.getParameterTypes();
    }
    
    public static <P, P1> Class<?>[] getParameterTypes(SFunc2<P, P1, ?> function) throws Exception {
        return function.getParameterTypes();
    }
    
    public static <E, P, R> Class<E> getImplClass(SFunction<P, R> function) {
        return function.getImplClass();
    }
    
    /**
     * 获取实例提供者类型
     *
     * <pre>
     * 例如：
     *
     *  class A {
     *      private Integer f1;
     *      public Integer getF1() {return f1;}
     *  }
     *  class A1 extends A {
     *  }
     *
     * getInstanceProviderType< A1::getF1 >   返回 Class< A1 >
     * </pre>
     */
    public static <E, P, R> Class<E> getInstanceProviderType(SFunction<P, R> function) {
        return function.getInstanceProviderType();
    }
    
    public static <P, P1, R> Class<?> getImplClass(SFunc2<P, P1, R> function) {
        return function.getImplClass();
    }
    
    public static <P, R> Lambda lambdaThis(SFunction<P, R> function) {
        return function;
    }
    
    
    public static <P, P1, R> Lambda lambdaThis(SFunc2<P, P1, R> function) {
        return function;
    }
    
    public static <P, P1, P2, R> Lambda lambdaThis(SFunc3<P, P1, P2, R> function) {
        return function;
    }
    
    public static <P, R> Field getField(SFunction<P, R> function) {
        try {
            SerializedLambda lambda = getSerializedLambda(function);
            Class<?> dType = function.getImplClass();
            Class<?> rType = function.getReturnType();
            String methodName = lambda.getImplMethodName();
            
            Exception ex = null;
            Field field = null;
            
            Class<?> impl = getImplClass(function);
            List<String> names = getLambdaPossibleMethods(lambda, function);
            String fieldName = null;
            
            for (int i = 0; i < names.size(); i++) {
                fieldName = names.get(i);
                
                // 先试用 原生方法名
                try {
                    field = impl.getDeclaredField(fieldName);
                    ex = null;
                    return field;
                }
                catch (Exception var18) {
                    if (ex == null) {ex = var18;}
                }
                
                // 首字母小写格式
                char ch = fieldName.charAt(0);
                if ('A' <= ch && ch <= 'Z') {ch = (char) (ch + 32);}
                fieldName = ch + fieldName.substring(1);
                try {
                    field = impl.getDeclaredField(fieldName);
                    ex = null;
                    return field;
                }
                catch (Exception var18) {
                    if (ex == null) {ex = var18;}
                }
            }
            if (ex != null) {throw ex;}
            if (field == null) {throw new NoSuchFieldException(fieldName);}
            
            return field;
        }
        catch (Exception e) {
            throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e.getMessage(), e);
        }
    }
    
    private static <P, R> List<String> getLambdaPossibleMethods(SerializedLambda lambda, SFunction<P, R> function) {
        String methodName = lambda.getImplMethodName();
        List<String> names = new ArrayList<>(5);
        
        String bool = "is";
        String get = "get";
        
        if (methodName.startsWith(get)) {
            // 去掉get 后面部分则视为 属性值
            names.add(methodName.substring(get.length()));
        }
        
        if (methodName.startsWith(bool)) {
            names.add(methodName);
            names.add(methodName.substring(bool.length()));
        }
        
        if (names.isEmpty()) {names.add(methodName);}
        return names;
    }
    
    public static <P, R> String getFieldName(SFunction<P, R> function) {
        try {
            return getField(function).getName();
        }
        catch (Exception e) {
            throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e.getMessage(), e);
        }
    }
}
